/* ------------------------------------------------------------------------
 *
 * tipc_net_topology.c
 *
 * Short description: TIPC network topology subscriber. Subscribes for
 *                    availability of nodes visible from local node, plus
 *                    the neighbors of those nodes.
 *
 *
 * ------------------------------------------------------------------------
 */

#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/poll.h>
#include <linux/tipc.h>

#ifndef TIPC_SERVICE_ADDR
#define TIPC_SERVICE_ADDR TIPC_ADDR_NAME
#endif

#define NODE_SUBSCR_TYPE 0
#define MAX_NODES 64
#define TITLE "TIPC Net Topology Agent: "
static __u32 self;

struct node {
	char neighbors[MAX_NODES];
	__u32 addr;
};
static struct node nodes[MAX_NODES];
static struct pollfd pfd[MAX_NODES];
static int node_cnt = 1;

static uint own_node_addr(void)
{
	struct sockaddr_tipc addr;
	socklen_t sz = sizeof(addr);
	int sd = 0;

	while (0 >= sd) {
		sleep(1);
		sd = socket(AF_TIPC, SOCK_RDM, 0);
	}
	if (getsockname(sd, (struct sockaddr *)&addr, &sz) < 0)
		perror("Failed to get sock address\n");
	close(sd);
	return addr.addr.id.node;
}

static int subscribe(__u32 node)
{
	struct sockaddr_tipc topsrv;
	struct tipc_subscr subscr;
	int sd = 0;

	/* Connect to topology server */

	memset(&topsrv, 0, sizeof(topsrv));
	topsrv.family = AF_TIPC;
	topsrv.addrtype = TIPC_SERVICE_ADDR;
	topsrv.addr.name.name.type = TIPC_TOP_SRV;
	topsrv.addr.name.name.instance = TIPC_TOP_SRV;
	topsrv.addr.name.domain = node;

	while (0 >= sd) {
		sd = socket (AF_TIPC, SOCK_SEQPACKET, 0);
		sleep(1);
	}
	if (0 > connect(sd, (struct sockaddr *)&topsrv, sizeof(topsrv))) {
		perror("Client: failed to connect to topology server");
		exit(1);
	}

	/* Subscribe to node events in range [<0.0.0>,<255.4095.4095>] */

	subscr.seq.type  = NODE_SUBSCR_TYPE;
	subscr.seq.lower = 0;
	subscr.seq.upper = ~0;
	subscr.timeout   = TIPC_WAIT_FOREVER;
	subscr.filter    = TIPC_SUB_PORTS;
	if (send(sd, &subscr, sizeof(subscr), 0) != sizeof(subscr)) {
		perror("Top subscr: Failed to subscribe at topology server");
		exit(1);
	}
	printf(TITLE "Subscribed for neigbors of %x\n", node);

	return sd;
}

static int find_index(__u32 node)
{
	int n;

	for (n = 0; n < node_cnt; n++) {
		if (node == nodes[n].addr)
			return n;
	}
	nodes[n].addr = node;
	node_cnt++;
	return n;
}

static void add_link(__u32 from, __u32 to)
{
	int nf = find_index(from);
	int nt = find_index(to);
	if (from != to) {
		if (from == self) {
			pfd[nt].fd = subscribe(to);
			pfd[nt].events = POLLIN;
		}
		if (!nodes[nf].neighbors[nt])
			printf(TITLE "Link %x <---> %x is up\n", from, to);
	}
	nodes[nf].neighbors[nt] = nodes[nt].neighbors[nf] = 1;
}

static void rem_link(__u32 from, __u32 to)
{
	int nf = find_index(from);
	int nt = find_index(to);

	if (nodes[nf].neighbors[nt] && (to != from))
		printf(TITLE "Link %x <---> %x went down\n", from, to);
	nodes[nf].neighbors[nt] = nodes[nt].neighbors[nf] = 0;
}

int main(int argc, char *argv[], char *dummy[])
{
	struct tipc_event evt;
	int n;

	self = own_node_addr();
	memset(nodes, 0, sizeof(nodes));
	memset(pfd, 0, sizeof(pfd));
	printf(TITLE "Started\n");

	/* Connect to local topology server */
	pfd[0].events = POLLIN | POLLHUP;
	pfd[0].fd = subscribe(self);
	nodes[0].addr = self;

	/* Now wait for subscriptions to fire */
	while (1) {
		if (0 >= poll(pfd, node_cnt, -1)) {
			printf(TITLE "poll() failed. Continuing.\n");
			continue;
		}
		for (n = 0; n < node_cnt; n++) {
			pfd[n].events = POLLIN | POLLHUP;
			if (!pfd[n].revents)
				continue;
			if (pfd[n].revents & POLLHUP) {
				pfd[n].fd = -pfd[n].fd;
				continue;
			}
			if (!pfd[n].revents & POLLIN)
				continue;
			if (recv(pfd[n].fd, &evt, sizeof(evt), 0) != sizeof(evt)) {
				printf(TITLE "recv() failed. Continuing.\n");
				continue;
			}
			if (evt.event == TIPC_PUBLISHED)
				add_link(nodes[n].addr, evt.found_lower);
			else if (evt.event == TIPC_WITHDRAWN)
				rem_link(nodes[n].addr, evt.found_lower);
			else
				printf(TITLE "Unexpected event. Continuing.\n");
		}
	}
}
